
package cubesystem3d;

import javafx.animation.KeyFrame;
import javafx.animation.KeyValue;
import javafx.animation.Timeline;
import javafx.application.Application;
import javafx.geometry.Point3D;
import javafx.scene.DepthTest;
import javafx.scene.Group;
import javafx.scene.Node;
import javafx.scene.PerspectiveCamera;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.RectangleBuilder;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Translate;
import javafx.stage.Stage;
import javafx.util.Duration;

import static javafx.scene.paint.Color.*;

import java.util.ArrayList;
import java.util.List;

/**
 * A sample that demonstrates a system of animated 3D cubes. When the
 * application runs in standalone mode, the scene must be constructed with
 * the depthBuffer argument set to true, and the root node must have depthTest
 * set to true.
 */
public class CubeSystem3D extends Application {

    private Timeline animation;

    private void init(Stage primaryStage) {
        Group root = new Group();
        root.setDepthTest(DepthTest.ENABLE);
        primaryStage.setResizable(false);
        primaryStage.setScene(new Scene(root, 500, 500, true));
        primaryStage.getScene().setCamera(new PerspectiveCamera());
        root.getTransforms().addAll(
                new Translate(250, 250),
                new Rotate(180, Rotate.X_AXIS)
        );
        root.getChildren().add(create3dContent());
    }

    private Node create3dContent() {
        Xform sceneRoot = new Xform();

        Xform cube1X = new Xform();
        Cube cube1 = new Cube(40, new Color(1, 0.9, 0, 1), 1);

        Xform[] cube1_X = new Xform[9];

        Cube[] cubes = new Cube[9];

        Color[] colors = {
                RED, ORANGE, CORNFLOWERBLUE, DARKGREEN, BLUE, PURPLE, BLUEVIOLET, DARKGOLDENROD, KHAKI
        };

        for (int i = 0; i < 9; i++) {
            cube1_X[i] = new Xform();
            cubes[i] = new Cube(i + 4, colors[i], 1);
        }

        sceneRoot.getChildren().add(cube1X);

        cube1X.getChildren().add(cube1);
        for (int i = 0; i < 9; i++) {
            cube1X.getChildren().add(cube1_X[i]);
            cube1_X[i].getChildren().add(cubes[i]);
            cubes[i].setTranslateX(20 * i + 40);
        }

        cube1_X[0].rx.setAngle(30);

        int[] as = {1, 5, 17, 6, 3, 12, 4, -6};
        for (int i = 1; i < 9; i++)
            cube1_X[i].rz.setAngle(as[i - 1] * 10);


        as = new int[]{8, 4, 3, 2, -4, -3, 1, 2, 3};

        add2(cube1X.ry, -1);
        add2(cube1X.rx, -1);

        for (int i = 0; i < 9; i++) {
            add2(cube1_X[i].ry, as[i]);
            add2(cubes[i].rx, i % 2 == 1 ? 5 : -5);
        }


        // Animate
        animation = new Timeline();
        animation.getKeyFrames().addAll(keyFrames.toArray(new KeyFrame[0]));
        animation.setCycleCount(Timeline.INDEFINITE);
        animation.play();

        return sceneRoot;
    }


    private void play() {
        animation.play();
    }

    private List<KeyFrame> keyFrames = new ArrayList<>();


    private void add2(Rotate rotate, int i) {
        double endTime = 4000;
        keyFrames.add(new KeyFrame(Duration.ZERO,
                new KeyValue(rotate.angleProperty(), 0)));
        keyFrames.add(new KeyFrame(new Duration(endTime),
                new KeyValue(rotate.angleProperty(), -360 * i)));
    }

    @Override
    public void stop() {
        animation.pause();
    }

    private class Xform extends Group0 {
        {
            getTransforms().addAll(rz, ry, rx);
        }
    }

    private class Group0 extends Group {
        final Rotate rx = new Rotate(0, Rotate.X_AXIS),
                ry = new Rotate(0, Rotate.Y_AXIS),
                rz = new Rotate(0, Rotate.Z_AXIS);
    }


    private class Cube extends Group0 {

        private List<Rectangle> list = new ArrayList<>();

        private RectangleBuilder<?> build(Color color, double xr, double yr) {
            return RectangleBuilder.create() // top face
                    .width(size).height(size)
                    .fill(color)
                    .translateX(-xr * size)
                    .translateY(-yr * size);
        }

        private RectangleBuilder<?> build(double r, double xr, double yr) {
            return build(color.deriveColor(0, 1, 1 - r * shade, 1), xr, yr);
        }

        private void add(double r, double xr, double yr, Point3D p) {
            list.add(build(r, xr, yr).rotationAxis(p).rotate(90).build());
        }

        private void add(RectangleBuilder<?> builder, double zr) {
            list.add(builder.translateZ(zr * size).build());
        }


        private double size, shade;
        private Color color;

        private Cube(double size, Color color, double shade) {
            this.size = size;
            this.shade = shade;
            this.color = color;

            add(build(0.5, 0.5, 0.5), 0.5);
            add(0.4, 0.5, 0, Rotate.X_AXIS);
            add(0.3, 1, 0.5, Rotate.Y_AXIS);
            add(0.2, 0, 0.5, Rotate.Y_AXIS);
            add(0.1, 0.5, 1, Rotate.X_AXIS);
            add(build(color, 0.5, 0.5), -0.5);

            getTransforms().addAll(rz, ry, rx);
            getChildren().addAll(list.toArray(new Rectangle[0]));
        }
    }

    @Override
    public void start(Stage primaryStage) {
        init(primaryStage);
        primaryStage.show();
        play();
    }

    public static void main(String[] args) {
        launch(args);
    }
}
